package app.biz.service.impl;

import app.biz.config.MongoDataSourceConfig;
import app.biz.model.ClientInfo;
import app.biz.model.EcgRequest;
import app.biz.model.EcgResponse;
import app.biz.service.atom.CodeMapper;
import app.biz.service.atom.EcgCoreFetcher;
import app.biz.service.aggregate.EcgResultService;
import app.framework.util.ConcurrentUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Projections;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.concurrent.ExecutionException;

import static com.mongodb.client.model.Filters.eq;

/** Created by shadowedge on 2018/11/24. */
@Service
public class EcgResultServiceImpl implements EcgResultService {
  private static final Logger logger = LoggerFactory.getLogger(EcgResultServiceImpl.class);

    @Autowired
    MongoDataSourceConfig mongo;

    @Autowired EcgCoreFetcher coreFetcher;

    @Autowired CodeMapper xkCodeMapper;

    /** 使用默认名称、默认线程池(10)*/
    static ListeningExecutorService resultPool = ConcurrentUtil.instance.getService();
    static ListeningExecutorService requestPool = ConcurrentUtil.instance.getService();

    /** 查询结果缓存 */
    LoadingCache<Long, JSONObject> resultCache = CacheBuilder.newBuilder()
            .maximumSize(1000)
//            .expireAfterWrite(7200, TimeUnit.SECONDS)
            .build(new CacheLoader<Long, JSONObject>() {
                @Override
                public JSONObject load(Long sid) {
                    // 这里是key根据实际去取值的方法
                    return findResultFromDB(sid);
                }
    });

  /**
   * 对请求内容和结果进行持久化处理
   * @param ecgRequest
   * @param result
   */
  private void saveRequestAndResult(EcgRequest ecgRequest, JSONObject result, ClientInfo clientInfo) {
        Document doc = Document.parse(result.toString());
        doc.append("sid",ecgRequest.getSid());
        doc.append("content",JSONObject.toJSON(ecgRequest));
        doc.append("md5",ecgRequest.getMd5());
        if(clientInfo!=null){
            doc.append("ip",clientInfo.getIP());
            doc.append("real_ip",clientInfo.getRealIP());
        }
        //补充日期信息
        doc.append("created", new Date()).append("createdTime", System.currentTimeMillis());
        getCollection().insertOne(doc);

        //移除bizlog中xml数据
        removeContentFromBizlog(ecgRequest.getSid());
        //放入缓存
        resultCache.put(ecgRequest.getSid(),result);
    }

    /**
    * 保存结果后，清除Bizlog的xml content数据，节约空间
    * @param sid
     */
    private void removeContentFromBizlog(long sid) {
        Document filter = new Document().append("sid", sid);
        Document content = new Document().append("content", "See Result!");
        Document update = new Document("$set",content);
        mongo.getCollecton("bizlog").updateOne(filter, update);
    }

    @Override //TODO:其实并不需要对外暴露这个方法
    @Deprecated
    public void saveRequestAndResultAsync(EcgRequest ecgRequest, JSONObject result) {
        saveRequestAndResultAsync(ecgRequest,result,null);
    }

    @Override
    public void saveRequestAndResultAsync(EcgRequest ecgRequest, JSONObject result, ClientInfo clientInfo) {
        try {
            //业务转码处理
            xkCodeMapper.addCode(result);
            resultPool.submit(() -> saveRequestAndResult(ecgRequest, result,clientInfo));
        } catch (Exception e) {
            logger.error("结果保存失败！结果内容：{}", e, result.toJSONString());
        }
    }

    @Override
    public JSONObject getRemoteResult(EcgRequest ecgRequest) {
        return getRemoteResult(ecgRequest,null);
    }

    @Override
    public JSONObject getRemoteResult(EcgRequest ecgRequest, ClientInfo clientInfo) {
        JSONObject result =  coreFetcher.fetchResult(ecgRequest);
        logger.info("算法请求原始结果:{}",result);
        //异步记录返回结果，不影响主流程
        saveRequestAndResultAsync(ecgRequest,result,clientInfo);
        //业务转码处理
        xkCodeMapper.addCode(result);
        return result;
    }

    /**
     * 异步获取和保存结果信息
     * @param ecgRequest
     */
    @Override
    public void getRemoteResultAsyncAndSave(EcgRequest ecgRequest) {
         getRemoteResultAsyncAndSave(ecgRequest,null);
    }

    @Override
    public void getRemoteResultAsyncAndSave(EcgRequest ecgRequest, ClientInfo clientInfo) {
        requestPool.submit(
                () -> {
                    JSONObject result = null;
                    try {
                        result = getRemoteResult(ecgRequest);
                    } catch (Exception e) {
                        logger.error("ECG服务调用失败！！", e);
                        //TODO:出错时也要持久化错误信息，否则本次调用将永无结果
                    }
                    JSONObject ecgResult = EcgResponse.instance.success(ecgRequest.getSid(), result);
                    //异步保存结果
                    saveRequestAndResultAsync(ecgRequest, ecgResult,clientInfo);
                });
    }

    @Override
    public void updateFeedback(JSONObject feedback) {
        Document filter = new Document().append("sid", feedback.get("sid"));
        Document feedbackDoc = new Document().append("feedback",Document.parse(feedback.toJSONString()));
        Document update = new Document("$set",feedbackDoc);
        getCollection().updateOne(filter, update);
        // 更新缓存
        resultCache.put(feedback.getLong("sid"), findResultFromDB(feedback.getLong("sid")));
    }

    @Override
  public JSONObject findResultBySid(long sid) {
        try {
            return resultCache.get(sid);
        } catch (ExecutionException e) {
           logger.error("",e);
        }
        return findResultFromDB(sid);
    }

    private JSONObject findResultFromDB(long sid) {
//         logger.info("执行数据库查询");
        Document doc =  getCollection()
                //构造查询条件
                .find(eq("sid", sid))
                //过滤返回内容:去掉XML原始数据
                .projection(Projections.exclude("content"))
                .first();
//    logger.info("db result:{}",doc);
        if(null == doc){
            return new JSONObject();
        }
        JSONObject result = new JSONObject();
        // 对查询结果进行提取封装
        // mongodb查询结果中会对long类型进行数据封装，如  "sid" : NumberLong(6616409131469153280)
        result.put("sid",doc.getLong("sid"));
        result.put("result",doc.get("result"));
        result.put("feedback",doc.get("feedback"));
        return result;
    }

    private MongoCollection<Document> getCollection() {
        return mongo.getCollecton("ecgresult");
    }
}
